package calvin.study.rabbitmq.consumer;

import java.util.Date;
import java.util.Map;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import com.rabbitmq.client.Channel;

import calvin.study.rabbitmq.dao.OrderDao;
import calvin.study.rabbitmq.model.order.Order;
import calvin.study.rabbitmq.service.impl.product.ProductService;
import cn.hutool.json.JSONUtil;

@Component
public class OrderReceiver {

	@Autowired
	private ProductService productService;
	@Autowired
	private OrderDao orderDao;

	// 解决消息重试的几种方案：
	// 1.控制重发次数 + 死信 + 死信队列
	// 2.try catch + 手动ack
	// 3.try catch + 手动ack + 死信队列 + 人工干预 √当前消费方式
	
	// 配置监听的哪一个队列，同时在没有queue和exchange的情况下会去创建并建立绑定关系
	@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "topic_order_queue", durable = "true"), exchange = @Exchange(value = "topic_order_exchange", durable = "true", type = ExchangeTypes.TOPIC, ignoreDeclarationExceptions = "true"), key = "order.*"))
	@RabbitHandler // 如果有消息过来，在消费的时候调用这个方法
	public void onOrderMessage(@Payload String payload, @Headers Map<String, Object> headers, Channel channel)
			throws Exception {
		System.out.println("---------" + new Date() + "收到消息，开始消费---------");
		Long deliveryTag = null;
		try {
			Order order = JSONUtil.toBean(payload, Order.class);
			System.out.println("订单ID：" + order.getOrderId());
			//异常场景
			//1.生产者向消息队列中发送消息，此时消费者服务宕机 消息会堆积在消息队列中，等待被消费。
			//2.商品库存被扣减后，此时消费者服务宕机，消息未被消费（消费者服务恢复，此时继续消费会产生幂等性问题）
			// 幂等性问题处理(查询该订单是否生成,库存是否减扣，生成则不重复减扣商品库存)
			
			/**
			 * Delivery Tag 用来标识信道中投递的消息。 RabbitMQ 推送消息给 Consumer 时，会附带一个 Delivery Tag， 以便
			 * Consumer 可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了。 RabbitMQ 保证在每个信道中，每条消息的 DeliveryTag
			 * 从 1 开始递增。
			 */
			deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);

			/**
			 * multiple 取值为 false 时，表示通知 RabbitMQ 当前消息被确认 如果为 true，则额外将比第一个参数指定的 delivery
			 * tag 小的消息一并确认
			 */
			boolean multiple = false;
			// 模拟应用发生异常
			// int i = 1 / 0;

			productService.updateProductNumber(order.getProductId());

			// 手工ACK,确认一条消息已经被消费
			channel.basicAck(deliveryTag, multiple);
			System.out.println("消费成功");

			
		} catch (Exception e) {
			/**
			 * 消费异常处理 进入死信队列
			 * 
			 * @param deliveryTag
			 * @param multiple    多条处理
			 * @param requeue     是否重发 false 不会重发，会把消息打入到死信队列  / true会死循环重发，
			 *    		如果使用true ，不要加try/catch否则会造成死循环
			 */
			channel.basicNack(deliveryTag, false, false);
		}
	}
}
